
#include <libxml/parser.h>
#include <libxml/xmlmemory.h>
#include <libxml/xmlwriter.h>

#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>

#include "fileutil.h"
#include "pkgutil.h"
#include "config.h"

#define PXLD_TEMP PXLD_PATH ".old"
#define PXLD_NEW PXL_DROP_PATH "/PXLdaemon"

int g_shouldShutdown;
time_t g_lastUpdate;

void signal_handler(int signal)
{
    if (signal == SIGTERM) {
        g_shouldShutdown = 1;    
    }
    return;
}

int commandPackageInstall(const char *packagefile)
{
    char *filebuffer;
    int filebufferlen;
    struct PxlPackage_info *installPkg;
    int result;

    /* We found a PXL Package */

    filebufferlen = strlen(PXL_DROP_PATH) + strlen(packagefile) + 3;
    filebuffer = (char *)malloc(filebufferlen);
    memset(filebuffer,0,filebufferlen);
    snprintf(filebuffer,filebufferlen - 1,"%s/%s", PXL_DROP_PATH, packagefile);
    
#ifdef DEBUG
    fprintf(stderr,"Found package: %s\n", filebuffer);     
#endif

    result = 0;

    /* Create our scratch-space */
    if (!exists_dir(PXL_SCRATCH)) {
        mkdir(PXL_SCRATCH,S_IRWXU | S_IRWXG | S_IRWXO);
    }
    
    installPkg = packageExtract(filebuffer);
    if (installPkg && installPkg->pkg_id) {
        char *manifestFile, *installManifestFile;
        int templen;
        
        result = 1;
        
        templen = strlen(installPkg->pkg_scratchdir) + strlen(PXL_MANIFEST) + 3;
        manifestFile = (char *)malloc(templen);
        memset(manifestFile,0,templen);
        snprintf(manifestFile,templen - 1,"%s/%s", installPkg->pkg_scratchdir, PXL_MANIFEST);
        
        templen = strlen(PXL_DB) + strlen(installPkg->pkg_id) + 9;
        installManifestFile = (char *)malloc(templen);
        memset(installManifestFile,0,templen);
        snprintf(installManifestFile,templen - 1,"%s/%s.plist",PXL_DB, installPkg->pkg_id);

        if (exists_file(installManifestFile)) {
            struct PxlPackage_info *existingPkg;
            int shouldUpgrade = 1;
            
            existingPkg = parsePackageInfo(installManifestFile);

            if (existingPkg->pkg_version && installPkg->pkg_version) {
                if (!strcasecmp(existingPkg->pkg_version,installPkg->pkg_version)) {
                    log_printf("IGNORE: %s %s (%s) is already installed.", installPkg->pkg_name, installPkg->pkg_version, installPkg->pkg_id);
                    shouldUpgrade = 0;
                }
                
                /* For now, any different version is an upgrade */
            }

            if (shouldUpgrade) {
                removePackage(existingPkg,1);
                installPackage(installPkg);
                writePackageInstalledManifest(installManifestFile,installPkg);
                log_printf("UPGRADE: %s %s (%s)", installPkg->pkg_name, installPkg->pkg_version, installPkg->pkg_id);
            }                                    
            freePackageInfo(existingPkg);
        }
        else
        {                                
            log_printf("INSTALL: %s %s (%s)", installPkg->pkg_name, installPkg->pkg_version, installPkg->pkg_id);
            installPackage(installPkg);
            writePackageInstalledManifest(installManifestFile,installPkg);
            
            if (installPkg->pkg_icon) {
                char *iconFile, *installIconFile;
                
                templen = strlen(installPkg->pkg_scratchdir) + strlen(installPkg->pkg_icon) + 3;
                iconFile = (char *)malloc(templen);
                memset(iconFile,0,templen);
                snprintf(iconFile,templen - 1,"%s/%s", installPkg->pkg_scratchdir, installPkg->pkg_icon);
                
                templen = strlen(PXL_DB) + strlen(installPkg->pkg_id) + 9;
                installIconFile = (char *)malloc(templen);
                memset(installIconFile,0,templen);
                snprintf(installIconFile,templen - 1,"%s/%s.png",PXL_DB, installPkg->pkg_id);
                
                copy_file(iconFile,installIconFile,0,0);
                free(iconFile);
                free(installIconFile);
            }
        }
        
        free(manifestFile);
        free(installManifestFile);
    }
    
    if (installPkg)
        freePackageInfo(installPkg);
    
    /* Remove package */
    unlink(filebuffer);
    free(filebuffer);
        
    return result;
}

int commandPackageRemove(const char *pkgId, int remove_all)
{
    char *manifestCheck;
    int manifestCheckLen, result;
    FILE *pxllog;
    
    result = 0;
    
    manifestCheckLen = strlen(PXL_DB) + strlen(pkgId) + 9;
    manifestCheck = (char *)malloc(manifestCheckLen);
    memset(manifestCheck,0,manifestCheckLen);
    snprintf(manifestCheck,manifestCheckLen - 1,"%s/%s.plist", PXL_DB, pkgId);

    if (exists_file(manifestCheck)) {
        struct PxlPackage_info *pkg;
        
        pkg = parsePackageInfo(manifestCheck);
        if (pkg) {
            log_printf("REMOVE: %s %s (%s)", 
                    pkg->pkg_name, pkg->pkg_version, pkg->pkg_id);

            result = removePackage(pkg, remove_all);
            if (result) {
                char *iconCheck;
                int iconCheckLen;

                unlink(manifestCheck);                
                iconCheckLen = strlen(PXL_DB) + strlen(pkgId) + 9;
                iconCheck = (char *)malloc(iconCheckLen);
                memset(iconCheck,0,iconCheckLen);
                snprintf(iconCheck,iconCheckLen - 1,"%s/%s.png", PXL_DB, pkgId);
                unlink(iconCheck);            
                free(iconCheck);
            }
                        
            freePackageInfo(pkg);
        }
    }
    
    free(manifestCheck);
    return result;    
}

int processCommandEntry(xmlDocPtr doc, xmlNodePtr node)
{
    xmlNodePtr curNode, dataNode;
    xmlChar *buffer;
    xmlChar *command = NULL;
    xmlChar *package = NULL;
    
    curNode = node;
    while (curNode) {
        if (curNode && !xmlStrcmp(curNode->name,(const xmlChar *)"key")) {
            buffer = xmlNodeListGetString(doc, curNode->xmlChildrenNode, 1);
            if (buffer && !xmlStrcmp(buffer,(const xmlChar *)"command")) {
                dataNode = curNode->next;
                while (dataNode && xmlIsBlankNode (dataNode)) {
                    dataNode = dataNode->next;
                }   
                
                if (dataNode && dataNode->name && !xmlStrcmp(dataNode->name,(const xmlChar *)"string")) {
                    command = xmlNodeListGetString(doc, dataNode->xmlChildrenNode, 1);
                }
                        
            }
            else if (buffer && !xmlStrcmp(buffer,(const xmlChar *)"package")) {
                dataNode = curNode->next;
                while (dataNode && xmlIsBlankNode (dataNode)) {
                    dataNode = dataNode->next;
                }   
                
                if (dataNode && dataNode->name && !xmlStrcmp(dataNode->name,(const xmlChar *)"string")) {
                    package = xmlNodeListGetString(doc, dataNode->xmlChildrenNode, 1);
                }
            }
        }
        curNode = curNode->next;
    }

    if (command && package) {
        if (!xmlStrcmp(command,(const xmlChar *)"install")) {
#ifdef DEBUG
            fprintf(stderr,"Command: install %s\n", (char *)package);
#endif
            commandPackageInstall((char *)package);
            g_lastUpdate = time(NULL);
        }
        else if (!xmlStrcmp(command,(const xmlChar *)"remove")) {
#ifdef DEBUG
            fprintf(stderr,"Command: remove %s\n", (char *)package);
#endif
            commandPackageRemove((char *)package,0);
            g_lastUpdate = time(NULL);
        }
        else if (!xmlStrcmp(command,(const xmlChar *)"obliterate")) {
#ifdef DEBUG
            fprintf(stderr,"Command: obliterate %s\n", (char *)package);
#endif
            commandPackageRemove((char *)package,1);
            g_lastUpdate = time(NULL);
        }
    }
    else if (command) {
        if (!xmlStrcmp(command,(const xmlChar *)"selfupdate")) {
#ifdef DEBUG
            fprintf(stderr,"Command: selfupdate\n");
#endif
            log_printf("PXLUPD: Updating PXL daemon.");
            rename(PXLD_PATH,PXLD_TEMP);
            copy_file(PXLD_NEW,PXLD_PATH,1,0);
            unlink(PXLD_NEW);
            return 0;
        }   
    }

    return 1;    
}

int processPickup()
{
    xmlDocPtr commands;
    xmlNodePtr rootNode, curNode, dataNode;
    xmlChar *buffer;
    int result = 0;

    commands = xmlParseFile(PXL_DOTHITCH);
    if (!commands)
        return 0;
        
    rootNode = xmlDocGetRootElement(commands);
    if (!rootNode) {
        xmlFreeDoc(commands);
        return 0;
    }

    if (xmlStrcmp(rootNode->name,(const xmlChar *) "plist")) {
        xmlFreeDoc(commands);
        return 0;
    }

    curNode = rootNode->xmlChildrenNode;
    while (curNode && xmlIsBlankNode (curNode)) {
        curNode = curNode->next;
    }
    
    /* Our first real plist child!  It better be a dict. */
    if ((curNode == NULL) || xmlStrcmp(curNode->name,(const xmlChar *) "dict")) {
        xmlFreeDoc(commands);
        return 0;    
    }

    curNode = curNode->xmlChildrenNode;
    
    while (curNode) {
        if (curNode && !xmlStrcmp(curNode->name,(const xmlChar *)"key")) {
            buffer = xmlNodeListGetString(commands, curNode->xmlChildrenNode, 1);
            if (!xmlStrcmp(buffer,(const xmlChar *)"commands")) {
                dataNode = curNode->next;
                while (dataNode && xmlIsBlankNode (dataNode)) {
                    dataNode = dataNode->next;
                }   
                
                if (dataNode && !xmlStrcmp(dataNode->name,(const xmlChar *)"array")) {
                    dataNode = dataNode->xmlChildrenNode;
                    while (dataNode && xmlIsBlankNode (dataNode)) {
                        dataNode = dataNode->next;
                    }   

                    while (dataNode) {
                        if (dataNode && !xmlStrcmp(dataNode->name,(const xmlChar *)"dict")) {
                            int newres;
                            newres = processCommandEntry(commands,dataNode->xmlChildrenNode);
                            result = result || newres;
                        }
                        dataNode = dataNode->next;
                    }
                }
            }
        }        
    
        curNode = curNode->next;
    }

    xmlFreeDoc(commands);

    /* Clean up scratch space */
    if (exists_dir(PXL_SCRATCH)) {
        delete_dir(PXL_SCRATCH);
    }    

    return result;
}

void writeInfo()
{
    xmlTextWriterPtr writer;
    FILE *pidfile;

    if (exists_file(PXL_INFO)) {
        unlink(PXL_INFO);
    }

    mkdirs(PXL_INFO);    
    writer = xmlNewTextWriterFilename(PXL_INFO,0);
    if (!writer)
        return;

    xmlTextWriterStartDocument(writer, "1.0", "UTF-8", NULL);
    xmlTextWriterSetIndent(writer,1);
    xmlTextWriterStartDTD(writer,(xmlChar *)"plist",(xmlChar *)"-//Apple Computer//DTD PLIST 1.0//EN",(xmlChar *)"http://www.apple.com/DTDs/PropertyList-1.0.dtd");
    xmlTextWriterEndDTD(writer);
    
    xmlTextWriterStartElement(writer,(xmlChar *)"plist");
    xmlTextWriterStartElement(writer,(xmlChar *)"dict");

    xmlTextWriterWriteElement(writer,(xmlChar *)"key",(xmlChar *)"RDPxlPackageFormat");    
    xmlTextWriterWriteFormatElement(writer,(xmlChar *)"integer","%d",PXL_MANIFEST_FORMAT);

    xmlTextWriterWriteElement(writer,(xmlChar *)"key",(xmlChar *)"RDPxlServerRelease");    
    xmlTextWriterWriteFormatElement(writer,(xmlChar *)"integer","%d",PXL_SERVER_VERSION);

    xmlTextWriterWriteElement(writer,(xmlChar *)"key",(xmlChar *)"RDPxlServerLastUpdateDB");
    xmlTextWriterWriteFormatElement(writer,(xmlChar *)"integer","%d",g_lastUpdate);

    xmlTextWriterEndElement(writer);    
    xmlTextWriterEndElement(writer);
    xmlTextWriterEndDocument(writer);
    xmlFreeTextWriter(writer);    
    
    if (exists_file(PXL_PIDFILE)) {
        unlink(PXL_PIDFILE);
    }
    
    pidfile = fopen(PXL_PIDFILE,"w");
    if (pidfile) {
        fprintf(pidfile,"%d",getpid());
        fclose(pidfile);
    }
}

int main(int argc, char **argv)
{
    DIR *pxldir;
    struct dirent *dp;
    
    g_shouldShutdown = 0;
    g_lastUpdate = time(NULL);
    
    /* Ugly, but when we use files to control self-update state or for
     * our secondary install method, the only way we can do this. */
    if (exists_file(UPDATE3_PATH)) {
        unlink(UPDATE3_PATH);
    }

    if (exists_file(PXLD_TEMP)) {
        unlink(PXLD_TEMP);
    }

    
    if (!exists_dir(PXL_DROP_PATH)) {
        mkdirs(PXL_DOTHITCH);
    }
    
    signal(SIGTERM,signal_handler);
    signal(SIGCHLD,signal_handler);
    
    writeInfo();
    
    while (!g_shouldShutdown) {
        if (exists_file(PXL_DOTHITCH)) {
            int should_restart;
            
            should_restart = processPickup();
            unlink(PXL_DOTHITCH);
            
            /* Restart SpringBoard to pick up any new apps */
            if (should_restart)
                springboard_restart();
                
            writeInfo();        
        }
        else {
#ifdef DEBUG
            fprintf(stderr,"No %s pickup file, skipping\n", PXL_DOTHITCH);
#endif
        }
        if (exists_file(PXLD_TEMP)) {
            if (exists_file(PXL_PIDFILE))
                unlink(PXL_PIDFILE);
            exit(0);
        }
        sleep(5);                        
    }
    
    if (exists_file(PXL_PIDFILE))
        unlink(PXL_PIDFILE);
    
    return 0;   
}
